# -*- mode: python -*-
###
#
# (c) 2000-2007 Wijnand Modderman <wijnand at tehmaze dot com>
# All rights reserved. See docs/LICENSE
#
###

__revision__ = '$Hg: .skell,v 979d136275fb Sun Jan 14 18:37:34 2007 +0100 $'

# system imports
import time
import math

# DDB imports
from ddb.lang import _
from ddb.log import log
from ddb.transport import ITransport, ITransportProtocol
from ddb.user import user
from ddb.settings import *

# Twisted imports
from twisted.internet import protocol, reactor
from twisted.words.protocols import irc

# Yes, this is really needed :-(
# <hack>
irc.RPL_TOPICAUTHOR = 333
irc.symbolic_to_numeric['RPL_TOPICAUTHOR'] = 333
irc.numeric_to_symbolic[333] = 'RPL_TOPICAUTHOR'
# </hack>

class ircProtocol(ITransportProtocol, irc.IRCClient):
    """IRC protocol skell."""

    type = 'IRC'

    # protocol compatibility aliases
    joinConference = lambda self, conference, key=None: self.join(conference, key)

    def __init__(self):
        self.capabilities = {}
        self.more         = {}
        self.lagMonitor   = False
        self.lagMeasure   = 0.0
        self.serverName   = ''
        ITransportProtocol.__init__(self)
        reactor.callLater(LAG_POLL, self.lagCheck)

    #
    # Generic connection-related
    #
    def connectionMade(self):
        if self.factory.config.has_key('identity'):
            self.nickname = self.factory.config['identity'][0]
        else:
            log.msg(_('Warning: no identity configured, fallback to default'))
        if self.factory.config.has_key('realname'):
            self.realname = self.factory.config['realname'][0]
        if self.factory.config.has_key('username'):
            self.username = self.factory.config['username'][0]
        self.identity = [self.nickname, '']
        ITransportProtocol.connectionMade(self)
        irc.IRCClient.connectionMade(self)
        self.lagMonitor = True

    def connectionLost(self, reason=''):
        ITransportProtocol.connectionLost(self, reason)
        irc.IRCClient.connectionLost(self, reason)
        self.lagMonitor = False

    def lineReceived(self, line):
        log.msg('<<< %s' % line, debug=True)
        irc.IRCClient.lineReceived(self, line)

    def sendLine(self, line):
        log.msg('>>> %s' % line, debug=True)
        irc.IRCClient.sendLine(self, line)

    def handleCommand(self, command, prefix, params):
        if self.callbacks.has_key(command):
            for callback in self.callbacks[command]:
                callback[0](callback[1], prefix, params)
            self.callbacks[command] = []
        irc.IRCClient.handleCommand(self, command, prefix, params)

    def lagCheck(self):
        if self.lagMonitor:
            self.lagMeasure = time.time()
            self.sendLine('PING %s' % self.serverName)
        reactor.callLater(LAG_POLL, self.lagCheck)

    def meet(self, event, identity, callback, errback):
        self.addCallback('RPL_WHOISUSER', callback, event)
        self.addCallback('RPL_ERR_NOSUCHNICK', errback, event)
        self.sendLine('WHOIS %s' % identity)

    def reply(self, event, message):
        if event.rawevent.target[0] not in PREFIX_CHANNEL:
            target  = event.user.identity
        else:
            target  = event.rawevent.target
            message = '%s: %s' % (event.user.identity, message)
        #self.sendLine('PRIVMSG %s%s' % (target, message))
        self.say(event, target, message)

    def say(self, event, target, message):
        command       = 'PRIVMSG %s :' % target
        messageLength = IRC_MAX_MSGLENGTH-len(command)-7 # -7 for the " (+x)"
        if len(message) > messageLength:
            # if the payload of the command prefix plus message are longer than
            # accepted, try to chop the message into parts that do fit
            #
            # max position in the message
            pos  = messageLength
            cpos = pos
            fpos = 0
            # look for ,. or space 20 chars back
            while (pos - cpos) < 20 and not fpos:
                if message[cpos] in '., ':
                    fpos = 1
                else:
                    cpos -= 1
            if not fpos:
                cpos = pos
            buff    = message[cpos:].lstrip()
            message = message[:cpos]
            # now scan the message for formatting chars, and close them
            formats = {'\002': 0, '\003': 0}
            for x in message:
                if formats.has_key(x):
                    formats[x] += 1
            for x in formats.keys():
                if formats[x]%2:
                    buff    = '%s%s' % (x, buff)
                    message = '%s%s' % (message, x)
            # add it to the buffer
            message = "%s (\002+%d\002)" % (message, math.ceil(float(len(buff))/float(messageLength)))
            self.more[target.lower()] = buff
        else:
            if self.more.has_key(target.lower()):
                del self.more[target.lower()]
        self.sendLine(command+message)
        

    #
    # Raw low-level IRC protocol handling
    #

    def irc_RPL_WELCOME(self, prefix, params):
        """The server greets us, it also contains our current nickname."""
        self.serverName  = prefix
        self.identity[0] = params[0]
        self.users.users['<SELF>'].identity = self.identity[0]
        self.signedOn()

    def irc_PONG(self, prefix, params):
        self.lag = time.time() - self.lagMeasure

    def irc_JOIN(self, prefix, params):
        nick   = prefix.split('!')[0]
        target = params[-1]
        if nick == self.nickname:
            self.joined(target)
        else:
            self.userJoined(prefix, target)

    def irc_PART(self, prefix, params):
        nick   = prefix.split('!')[0]
        target = params[0]
        if nick == self.nickname:
            self.left(target)
        else:
            self.userLeft(nick, target)

    def irc_TOPIC(self, prefix, params):
        """Someone in the channel set the topic."""
        target = params[0]
        topic  = params[1]
        self.conferences.set(target, topic={
            'setby': prefix,
            'setat': time.time(),
            'topic': topic
            })

    def irc_RPL_TOPIC(self, prefix, params):
        """I just joined the channel, and the server is telling me the current topic."""
        self.irc_TOPIC(prefix, params[1:])

    def irc_RPL_NOTOPIC(self, prefix, params):
        self.irc_TOPIC(prefix, [params[1], ''])

    def irc_333(self, prefix, params):
        target = params[1]
        prefix = params[2]
        setat  = float(params[3])
        topic  = self.conferences.get(target, 'topic')
        topic  = topic['topic']
        self.conferences.set(target, topic={
            'setby': prefix,
            'setat': setat,
            'topic': topic
            })

    def irc_RPL_WHOREPLY(self, prefix, params):
        _, target, ident, host, server, identity, status, realname = params
        mask = '%s!%s@%s' % (identity, ident, host)
        self.userJoined(mask, target)

    def irc_RPL_ENDOFWHO(self, prefix, params):
        """End of /WHO reply."""
        target = params[1]
        self.sendLine('MODE %s' % target)
#        # special modes have to be requested seperately
        reactor.callLater(2, self.sendLine, 'MODE %s +b' % target)
        if self.capable('exepts'):
            reactor.callLater(2, self.sendLine, 'MODE %s +%s' % (target, self.capable('exepts')))
        if self.capable('invex'):
            reactor.callLater(2, self.sendLine, 'MODE %s +%s' % (target, self.capable('invex')))
#        self.sendLine('TOPIC %s' % target)
    
    def irc_RPL_BOUNCE(self, prefix, params):
        """This is b0rked in Twisted, because the last line may vary...
        *sigh*"""
        if 'are supported' in params[-1]:
            self.isupport(params[1:-1])
        else:
            self.bounce(params[1])
 
    def isupport(self, options):
        """Parse a list of supported capabilities."""
        for option in options:
            key   = option
            value = True
            if '=' in key:
                key, value = key.split('=', 1)
            key   = key.lower()
            self.capabilities[key] = value
        print self.capabilities

    def capable(self, capability):
        if self.capabilities.has_key(capability):
            return self.capabilities[capability]
        return None


    def op(self, target, victims, mod='+', mode='o'):
        """Give victims operator status on target."""
        try:
            modeLength = self.capable('modes')
            if modeLength:
                modeLength = int(modeLength)
        except:
            modeLength = 3
        else:
            modeLength = 3
        while victims:
            ops     = victims[:modeLength]
            victims = victims[modeLength:]
            self.sendLine('MODE %s %s%s %s' % (target, mod, mode*len(ops),
                ' '.join(ops)))

    def deop(self, target, victims):
        """Take away operator status from victims on target."""
        self.op(target, victims, '-')

    def voice(self, target, victims):
        self.op(target, victims, '+', 'v')

    def voice(self, target, victims):
        self.op(target, victims, '-', 'v')

    def halfop(self, target, victims):
        self.op(target, victims, '+', 'h')

    def dehalfop(self, target, victims):
        self.op(target, victims, '-', 'h')

    #
    # Status updates about myself
    #

    def signedOn(self):
        """Logged in successfully."""
        ITransportProtocol.signedOn(self)
        print self.factory.config
        if self.factory.config.has_key('conference'):
            for conference in self.factory.config['conference']:
                if ' ' in conference:
                    conference, key = conference.split(' ', 1)
                else:
                    key = None
                self.join(conference, key)
                self.conferences.add(conference, created={}, topic={}, bans={}, exepts={}, 
                    invex={}, modes={})

    def joined(self, target):
        ITransportProtocol.openedConference(self, target)
        self.sendLine('WHO %s' % target)

    def parted(self, target):
        self.users.removeConference(target)
        ITransportProtocol.closedConference(self, target)
 
    def privmsg(self, senderMask, target, message):
        """We received a message."""
        senderIdentity = senderMask.split('!')[0]
        ITransportProtocol.message(self, senderIdentity, senderMask, target, message)

    #
    # Status updates about others
    #

    def userJoined(self, senderMask, target):
        senderIdentity = senderMask.split('!')[0]
        ITransportProtocol.joinedConference(self, senderIdentity, senderMask, target)

    def userLeft(self, senderMask, target):
        senderIdentity = senderMask.split('!')[0]
        ITransportProtocol.partedConference(self, senderIdentity, senderMask, target)

class ircFactory(protocol.ReconnectingClientFactory):
    protocol = ircProtocol

    def __init__(self, manager, name, config):
        self.manager = manager
        self.name    = name
        self.config  = config

class transport(ITransport):
    """IRC Transport base class."""
    protocol = ircProtocol
    factory  = ircFactory
    #event    = ircEvent
    
    def __init__(self, manager, name, config):
        ITransport.__init__(self, manager, name, config)
        log.msg('starting %s' % str(self.__class__))
        try:
            serverHost = config['host'][0]
            try:
                serverPort = int(config['port'][0])
            except KeyError:
                serverPort = 6667
        except KeyError, e:
            log.msg(_('Could not start transport, missing configuration option "%s"') % str(e))
            return
        self.server = ircFactory(manager, name, config)
        reactor.connectTCP(serverHost, serverPort, self.server)

    def start(self):
        self.run = True

    def stop(self):
        self.run = False

# vim:ts=4:sw=4:et:
